package evolution;

import static evolution.VersionedDirectoryTreeBuilder.VERSIONING_DIR;

import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;

public class VersionedDirectoryTree implements VersionedResourceLookup, Iterable<VersionedResource>
{
	private final Directory repoDir;

	private Map<UUID, VersionedResource> versionedResources = new HashMap<UUID, VersionedResource>();

	private final UUIDGenerator uuidGenerator;

	private final ResourceFactory resourceFactory;

	private VersionedResource rootElement;

	private final Directory dir;

	public VersionedDirectoryTree(Directory dir, Directory repoDir, ResourceFactory resourceFactory, UUIDGenerator uuidGenerator) throws ResourceException,
			VersionedDirectoryTreeException
	{
		this.dir = dir;
		this.repoDir = repoDir;
		this.resourceFactory = resourceFactory;
		this.uuidGenerator = uuidGenerator;
		for (Resource member : repoDir)
		{
			if (member instanceof Directory)
			{
				UUID uuid = UUID.fromString(member.getName());
				VersionedResource el = new VersionedResource((Directory) member, this);
				versionedResources.put(uuid, el);
			}
		}
	}

	public VersionedResource lookupElement(UUID uuid)
	{
		return versionedResources.get(uuid);
	}

	public VersionedResource getRootElement()
	{
		return rootElement;
	}

	public long countElements()
	{
		return (long) versionedResources.size();
	}

	public VersionedResource addRootResource(Directory dir) throws VersionedDirectoryTreeException, ResourceException
	{
		if (rootElement != null)
		{
			throw new IllegalStateException("A root element has already been added");
		}
		if (dir.getName().equals(VERSIONING_DIR))
		{
			throw new VersionedDirectoryTreeException("Cannot add a directory named " + VERSIONING_DIR);
		}

		rootElement = createVersionedResource(dir, "/", null);
		return rootElement;
	}

	public VersionedResource addResource(Resource resource, VersionedResource parent) throws VersionedDirectoryTreeException, ResourceException
	{
		if (resource.getName().equals(VERSIONING_DIR) && resource instanceof Directory)
		{
			throw new VersionedDirectoryTreeException("Cannot add a directory named " + VERSIONING_DIR);
		}

		// TODO: Check that it is really is a new element and we aren't already tracking it.
		return createVersionedResource(resource, resource.getName(), parent);
	}

	private void putElement(VersionedResource el)
	{
		versionedResources.put(el.getUUID(), el);
	}

	private VersionedResource createVersionedResource(Resource resource, String name, VersionedResource parent) throws ResourceException,
			VersionedDirectoryTreeException
	{
		Directory newElementDir = resourceFactory.createDirectory(uuidGenerator.generateUUID().toString());
		repoDir.addMember(newElementDir);

		File changeLogResource = resourceFactory.createFile(VersionedResource.CHANGE_LOG);

		PrintWriter writer = new PrintWriter(changeLogResource.getWriter());
		writer.println("Name: " + name);
		writer.println("Date: " + resource.getLastModifiedTime());
		if (parent != null)
		{
			writer.println("Parent: " + parent.getUUID());
		}
		writer.close();
		newElementDir.addMember(changeLogResource);

		VersionedResource result = new VersionedResource(newElementDir, this);
		putElement(result);
		return result;
	}

	public VersionedDirectoryTreeStatus getStatus()
	{
		// TODO: Walk the tree, find out which are known elements, which are known but modified elements, which are 'private' files (unknown to this repo)
		// TODO: Might need some unit tests for this directory walk...
		// TODO: Visitor pattern is ideal for this

		StatusCollatingResourceVisitor visitor = new StatusCollatingResourceVisitor(this);

		visitor.visitDirectory(dir);
		for (Resource res : dir)
		{
			if (res instanceof Directory && res.getName().equals(VERSIONING_DIR))
			{
				continue;
			}
			if (res instanceof Directory)
			{
				((Directory) res).accept(visitor);
			}
			else if (res instanceof File)
			{
				visitor.visitFile((File) res);
			}
		}
		return visitor.getResult();
	}

	public VersionedResource searchElementByPath(String path)
	{
		// Do a brute force full scan of each versioned resource.
		// In the future, use indexing strategies of increasing sophistication.
		for (VersionedResource res : versionedResources.values())
		{
			// System.out.println("VDT: resource "+res+" path is "+res.getPath());
			if (res.getPath().equals(path))
			{
				return res;
			}
		}
		return null;
	}

	public Iterator<VersionedResource> iterator()
	{
		return versionedResources.values().iterator();
	}
}
